#include "openglpainter.h"

OpenGLPainter::OpenGLPainter() {
    context = new QOpenGLContext();
    if (!context->create()) {
        qFatal("failed to create opengl context!");
    }

    surface = new QOffscreenSurface();
    surface->setFormat(context->format());
    surface->create();

    if (!context->makeCurrent(surface)) {
        qFatal("failed to active opengl context!");
    }
    f = context->functions();
    ef = context->extraFunctions();
    device = new QOpenGLPaintDevice();

    device->setSize(drawSize);

    QOpenGLFramebufferObjectFormat fboFormat;
    fboFormat.setSamples(4);
    fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    fbo = new QOpenGLFramebufferObject(drawSize, fboFormat);

    QOpenGLFramebufferObjectFormat resolveFboFormat;
    resolveFboFormat.setSamples(0);
    resolveFboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    resolveFbo = new QOpenGLFramebufferObject(drawSize, resolveFboFormat);

    f->glGenBuffers(1, &pbo);
    f->glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    f->glBufferData(GL_PIXEL_PACK_BUFFER, drawSize.width() * drawSize.height() * 4, NULL, GL_STREAM_READ);
    f->glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}

OpenGLPainter::~OpenGLPainter() {
    if (context && surface && context->makeCurrent(surface)) {
        f->glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
        if (data) {
            ef->glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
            data = nullptr;
        }
        f->glDeleteBuffers(1, &pbo);
        f->glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
        delete fbo;
        delete resolveFbo;
        delete device;
        context->doneCurrent();
    }
    delete surface;
    delete context;
}

QImage OpenGLPainter::Paint() {
    f->glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle());
    f->glViewport(0, 0, drawSize.width(), drawSize.height());
    f->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    f->glClear(GL_COLOR_BUFFER_BIT);

    QPainter painter;
    bool success = painter.begin(device);
    if (!success) {
        qFatal() << "QPainter::begin() failed!";
    } else {
        QPainterPath path;
        path.addRect(QRectF(0, 0, 500, 500));
        QBrush brush;
        brush.setColor(QColor(255, 0, 0));
        brush.setStyle(Qt::BrushStyle::SolidPattern);
        painter.fillPath(path, brush);
        painter.drawPath(path);
        painter.end();
    }

    f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
    f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFbo->handle());
    ef->glBlitFramebuffer(0, 0, drawSize.width(), drawSize.height(),
                          0, 0, drawSize.width(), drawSize.height(),
                          GL_COLOR_BUFFER_BIT, GL_NEAREST);

    f->glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFbo->handle());
    f->glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    f->glReadPixels(0, 0, drawSize.width(), drawSize.height(), GL_RGBA, GL_UNSIGNED_BYTE, 0);
    f->glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    if (data == nullptr) {
        f->glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
        data = static_cast<uint32_t *>(ef->glMapBufferRange(
        GL_PIXEL_PACK_BUFFER, 0, drawSize.width() * drawSize.height() * 4, GL_MAP_READ_BIT));
        f->glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    }

    if (!data) {
        qFatal("failed to map pbo!");
    }

    return QImage((uchar *) data, drawSize.width(), drawSize.height(), QImage::Format_RGBA8888);
}
